﻿using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PioneerBoilerplate.Repository.UnitOfWorks
{
    public class AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext> where TDbContext : DbContext
    {
        private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>();

        public static UnitOfWorkHandle<TDbContext> Current
        {
            get { return GetCurrentUow(); }
            set { SetCurrentUow(value); }
        }



        private static UnitOfWorkHandle<TDbContext> GetCurrentUow()
        {
            var uow = AsyncLocalUow.Value?.UnitOfWorkHandle;
            if (uow == null)
            {
                return null;
            }

            if (uow.IsDisposed)
            {
                AsyncLocalUow.Value = null;
                return null;
            }

            return uow;
        }

        private static void SetCurrentUow(UnitOfWorkHandle<TDbContext> value)
        {
            lock (AsyncLocalUow)
            {
                if (value == null)
                {
                    if (AsyncLocalUow.Value == null)
                    {
                        return;
                    }

                    if (AsyncLocalUow.Value.Outer == null)
                    {
                        AsyncLocalUow.Value.UnitOfWorkHandle = null;
                        AsyncLocalUow.Value = null;
                        return;
                    }
                    var oldValue = AsyncLocalUow.Value;
                    AsyncLocalUow.Value = AsyncLocalUow.Value.Outer;
                    oldValue.Outer = null;
                }
                else
                {
                    if (AsyncLocalUow.Value?.UnitOfWorkHandle == null)
                    {
                        if (AsyncLocalUow.Value != null)
                        {
                            AsyncLocalUow.Value.UnitOfWorkHandle = value;
                        }

                        AsyncLocalUow.Value = new LocalUowWrapper(value);
                        return;
                    }
                    var newValue = new LocalUowWrapper(value) { Outer = AsyncLocalUow.Value };
                    AsyncLocalUow.Value = newValue;
                }
            }
        }

        private class LocalUowWrapper
        {
            public UnitOfWorkHandle<TDbContext> UnitOfWorkHandle { get; set; }

            public LocalUowWrapper Outer { get; set; }

            public LocalUowWrapper(UnitOfWorkHandle<TDbContext> unitOfWorkHandle)
            {
                UnitOfWorkHandle = unitOfWorkHandle;
            }
        }
    }
}
